﻿using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml;
using WorkFlow.Data.Model;

namespace WorkFlow.Data
{
    public class WorkFlowTool
    {
        private const string ConfigName_ThirdApiAddress = "ThirdApiAddress";
        public static string timeStyle = "yyyy-MM-dd HH:mm:ss";
        private string workFlowXmlPath = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "WorkFlow";
        private Dictionary<string, XmlDocument> workFlowXmlDict = new Dictionary<string, XmlDocument>();
        private Dictionary<string, MasterType> MasterTypeDict = new Dictionary<string, MasterType>();
        public SqlHelper sqlHelper = new SqlHelper();

        public static string ThirdApiAddress;
        DataManager _DataManager;
        public WorkFlowTool(DataManager dataManager)
        {
            _DataManager = dataManager;
        }
        public static void InitConfig(IConfiguration configuration)
        {
            string thirdApiAddress = configuration.GetValue<string>(ConfigName_ThirdApiAddress);
            if (!string.IsNullOrEmpty(thirdApiAddress))
            {
                ThirdApiAddress = thirdApiAddress;
            }

        }
        private MasterType GetMasterType(string baseID)
        {
            baseID = baseID.Replace("{", "").Replace("}", "");
            if (MasterTypeDict.Count == 0)
            {
                string sql = $"select * from shapeinfo";
                DataTable dataTable = new DataTable();
                dataTable = sqlHelper.myDataSet(sql).Tables[0];
                foreach (DataRow dr in dataTable.Rows)
                {
                    MasterType masterType = MasterType.Other;
                    switch (dr["Type"].ToString())
                    {
                        case "Start":
                            masterType = MasterType.Start;
                            break;
                        case "End":
                            masterType = MasterType.End;
                            break;
                        case "Automation":
                            masterType = MasterType.Automation;
                            break;
                        case "WaitAutomation":
                            masterType = MasterType.WaitAutomation;
                            break;
                        case "Interface":
                            masterType = MasterType.Interface;
                            break;
                        case "Dynamic connector":
                            masterType = MasterType.DynamicConnector;
                            break;
                        case "Line arc":
                            masterType = MasterType.LineArc;
                            break;
                        case "IsSubprocess":
                            masterType = MasterType.IsSubprocess;
                            break;
                        default:
                            break;
                    }
                    MasterTypeDict.Add(dr["BaseID"].ToString(), masterType);
                }
            }
            if (MasterTypeDict.ContainsKey(baseID))
            {
                return MasterTypeDict[baseID];
            }
            return MasterType.LineArc;
        }
        private XmlDocument GetWorkFlowXml(string name)
        {
            if (!workFlowXmlDict.ContainsKey(name))
            {
                XmlDocument workFlowXml = new XmlDocument();
                workFlowXml.Load(Path.Combine(workFlowXmlPath, name + ".vdx"));
                XmlNamespaceManager xnm = new XmlNamespaceManager(workFlowXml.NameTable);
                xnm.AddNamespace("X", "http://schemas.microsoft.com/visio/2003/core");

                // Create new xml that ower need node data
                XmlDocument newXml = new XmlDocument();
                // Create root Node
                XmlNode rootNode = newXml.CreateNode(XmlNodeType.Element, "root", null);
                // Create Masters
                XmlNode mastersNode = newXml.CreateNode(XmlNodeType.Element, "Masters", null);
                // Create Shapes
                XmlNode shapesNode = newXml.CreateNode(XmlNodeType.Element, "Shapes", null);
                // Create Connects
                XmlNode connectsNode = newXml.CreateNode(XmlNodeType.Element, "Connects", null);

                rootNode.AppendChild(mastersNode);
                rootNode.AppendChild(shapesNode);
                rootNode.AppendChild(connectsNode);
                newXml.AppendChild(rootNode);


                // Add all shapes
                XmlNodeList shapeNodeList = workFlowXml.SelectNodes("/X:VisioDocument/X:Pages/X:Page/X:Shapes/X:Shape", xnm);
                foreach (XmlNode shapeNode in shapeNodeList)
                {
                    // Create new shapeNode
                    XmlNode newShapeNode = newXml.CreateNode(XmlNodeType.Element, "Shape", null);

                    #region Add Attribute
                    XmlAttribute idAtr = newXml.CreateAttribute("ID");
                    idAtr.Value = shapeNode.Attributes["ID"].Value;
                    XmlAttribute masterAtr = newXml.CreateAttribute("Master");
                    masterAtr.Value = shapeNode.Attributes["Master"].Value;
                    // Add step name
                    XmlNode textNode = shapeNode.SelectSingleNode("./X:Text", xnm);
                    XmlAttribute nameAtr = newXml.CreateAttribute("Name");
                    nameAtr.Value = textNode != null ? textNode.InnerText.Replace("\r", "").Replace("\n", "") : "";

                    // Add config
                    XmlNodeList propValueNodes = shapeNode.SelectNodes("./X:Prop", xnm);
                    foreach (XmlNode propValueNode in propValueNodes)
                    {
                        XmlNode valueNode = propValueNode.SelectSingleNode("./X:Value", xnm);
                        XmlNode labelNode = propValueNode.SelectSingleNode("./X:Label", xnm);
                        if (labelNode == null || valueNode == null)
                        {
                            continue;
                        }
                        if (!string.IsNullOrEmpty(labelNode.InnerText))
                        {
                            XmlNode itemNode = newXml.CreateNode(XmlNodeType.Element, labelNode.InnerText, null);
                            itemNode.InnerText = valueNode.InnerText;
                            newShapeNode.AppendChild(itemNode);
                        }
                    }

                    newShapeNode.Attributes.Append(idAtr);
                    newShapeNode.Attributes.Append(nameAtr);
                    newShapeNode.Attributes.Append(masterAtr);
                    shapesNode.AppendChild(newShapeNode);
                    #endregion

                    #region Add Master
                    XmlNode masterNode = workFlowXml.SelectSingleNode($"/X:VisioDocument/X:Masters/X:Master[@ID='{masterAtr.Value}']", xnm);
                    XmlNode newMasterNode = newXml.CreateNode(XmlNodeType.Element, "Master", null);
                    foreach (XmlAttribute atrItem in masterNode.Attributes)
                    {
                        XmlAttribute newAtr = newXml.CreateAttribute(atrItem.Name);
                        newAtr.Value = atrItem.Value;
                        newMasterNode.Attributes.Append(newAtr);
                    }
                    mastersNode.AppendChild(newMasterNode);
                    #endregion
                }

                // Add Connects
                XmlNodeList connectNodeList = workFlowXml.SelectNodes("/X:VisioDocument/X:Pages/X:Page/X:Connects/X:Connect", xnm);
                foreach (XmlNode connectNode in connectNodeList)
                {
                    XmlNode newConnectNode = newXml.CreateNode(XmlNodeType.Element, "Connect", null);
                    foreach (XmlAttribute atrItem in connectNode.Attributes)
                    {
                        XmlAttribute newAtr = newXml.CreateAttribute(atrItem.Name);
                        newAtr.Value = atrItem.Value;
                        newConnectNode.Attributes.Append(newAtr);
                    }
                    connectsNode.AppendChild(newConnectNode);
                }
                string tempPath = Path.Combine(workFlowXmlPath, @"Temp\" + name + ".xml");
                if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
                }

                newXml.Save(tempPath);
                workFlowXmlDict.Add(name, newXml);
            }
            return workFlowXmlDict[name];
        }

        #region WorkFlowFile tools
        public WorkFlowFile GetStarStepIDbyWorkFlowName(string workFlowName)
        {
            XmlDocument xmlDoc = GetWorkFlowXml(workFlowName);
            // Get start master
            XmlNodeList xmlNodeList = xmlDoc.SelectNodes("//Master");
            foreach (XmlNode item in xmlNodeList)
            {
                string masterID = item.Attributes["ID"].Value;
                string baseID = item.Attributes["BaseID"].Value;
                MasterType masterType = GetMasterType(baseID);
                if (masterType == MasterType.Start)
                {
                    // Get Start shape
                    XmlNode xmlNode = xmlDoc.SelectSingleNode($"//Shape[@Master='{masterID}']");
                    if (xmlNode != null)
                    {
                        string stepID = xmlNode.Attributes["ID"].Value;
                        return GetWorkFlowFileByStepID(workFlowName, stepID);
                    }
                }
            }
            return null;
        }

        public WorkFlowFile GetWorkFlowFileByStepID(string workFlowName, string stepID)
        {
            XmlDocument xmlDoc = GetWorkFlowXml(workFlowName);
            XmlNode shapeNode = xmlDoc.SelectSingleNode($"//Shape[@ID='{stepID}']");
            //Get step id
            string id = shapeNode.Attributes["ID"].Value;
            // Get step name
            string stepName = shapeNode.Attributes["Name"].Value;
            // Get next master node
            string masterId = shapeNode.Attributes["Master"].Value;
            XmlNode mastarNode = xmlDoc.SelectSingleNode($"//Master[@ID='{masterId}']");
            // Create start WorkFlowFile
            MasterType masterType = GetMasterType(mastarNode.Attributes["BaseID"].Value);
            WorkFlowFile workFlowFile = new WorkFlowFile();
            workFlowFile.ShapeType = masterType;
            workFlowFile.StepID = stepID;
            workFlowFile.StepName = stepName;

            workFlowFile.ShapeNode = shapeNode;

            return workFlowFile;
        }
        public WorkFlowFile GetCurrentTaskNode(string detailID)
        {
            string sql = $"SELECT * FROM workflow.workflowinfo "
+ $" left join workflow.workflowdetail on workflow.workflowinfo.ID = workflow.workflowdetail.MainID"
+ $" where workflow.workflowdetail.ID = '{detailID}'";
            DataTable mainDT = new DataTable();
            mainDT = sqlHelper.myDataSet(sql).Tables[0];
            string workFlowName = mainDT.Rows[0]["WorkFlowName"].ToString();
            string currentStepID = mainDT.Rows[0]["StepID"].ToString();
            return GetWorkFlowFileByStepID(workFlowName, currentStepID);

        }
        public List<WorkFlowFile> GetNetStepID(string currentID, string workFlowName, out List<WorkFlowFile> lineFiles)
        {
            lineFiles = new List<WorkFlowFile>();
            XmlDocument xmlDoc = GetWorkFlowXml(workFlowName);
            List<WorkFlowFile> workFlowFiles = new List<WorkFlowFile>();
            // If currentID equal -1 then find start node 
            if (currentID == "-1")
            {
                WorkFlowFile workFlowFile = GetStartStepID(xmlDoc);
                workFlowFiles.Add(workFlowFile);
            }

            XmlNodeList connectNodeList = xmlDoc.SelectNodes($"//Connect[@ToSheet='{currentID}']");
            foreach (XmlNode connectNode in connectNodeList)
            {
                // Get Line of the CurrentNode-NextNode
                string fromSheet = connectNode.Attributes["FromSheet"].Value;
                //Get Line Shape
                XmlNode lineShape = xmlDoc.SelectSingleNode($"//Shape[@ID='{fromSheet}']");
                // Get Next ShapeID
                XmlNodeList nextLineNodes = xmlDoc.SelectNodes($"//Connect[@FromSheet='{fromSheet}']");
                foreach (XmlNode nextLineNode in nextLineNodes)
                {
                    string toSheet = nextLineNode.Attributes["ToSheet"].Value;
                    string fromCell = nextLineNode.Attributes["FromCell"].Value;
                    if (toSheet != currentID && fromCell == "EndX")
                    {
                        // Get next shape node
                        XmlNode shapeNode = xmlDoc.SelectSingleNode($"//Shape[@ID='{toSheet}']");
                        //Get step id
                        string id = shapeNode.Attributes["ID"].Value;
                        // Get step name
                        string stepName = shapeNode.Attributes["Name"].Value;
                        // Get next master node
                        string masterId = shapeNode.Attributes["Master"].Value;
                        XmlNode mastarNode = xmlDoc.SelectSingleNode($"//Master[@ID='{masterId}']");
                        // Create start WorkFlowFile
                        MasterType masterType = GetMasterType(mastarNode.Attributes["BaseID"].Value);
                        WorkFlowFile workFlowFile = new WorkFlowFile();
                        workFlowFile.ShapeType = masterType;
                        workFlowFile.StepID = toSheet;
                        workFlowFile.StepName = stepName;

                        workFlowFile.ShapeNode = shapeNode;

                        workFlowFiles.Add(workFlowFile);
                        // Create Line File
                        // Get next master node
                        string lineMasterId = lineShape.Attributes["Master"].Value;
                        XmlNode lineMastarNode = xmlDoc.SelectSingleNode($"//Master[@ID='{lineMasterId}']");
                        // Create start WorkFlowFile
                        MasterType lineMasterType = GetMasterType(lineMastarNode.Attributes["BaseID"].Value);

                        WorkFlowFile lineFile = new WorkFlowFile();
                        lineFile.ShapeType = lineMasterType;
                        lineFile.StepID = fromSheet;
                        lineFile.StepName = lineShape.Attributes["Name"].Value;
                        lineFile.ShapeNode = lineShape;
                        lineFiles.Add(lineFile);
                    }
                }
            }
            return workFlowFiles;
        }

        private WorkFlowFile GetStartStepID(XmlDocument xmlDoc)
        {
            XmlNodeList shapeNodeList = xmlDoc.SelectNodes("/root/Shapes/Shape");
            for (int i = 0; i < shapeNodeList.Count; i++)
            {
                XmlNode shapeNode = shapeNodeList[i];
                string masterID = shapeNode.Attributes["Master"].Value;
                XmlNode masterNode = xmlDoc.SelectSingleNode($"//Master[@ID='{masterID}']");
                string BaseID = masterNode.Attributes["BaseID"].Value;
                MasterType masterType = GetMasterType(BaseID);
                if (masterType == MasterType.Start)
                {
                    string stepID = shapeNode.Attributes["ID"].Value;
                    string stepName = shapeNode.Attributes["Name"].Value;
                    // Create start WorkFlowFile
                    WorkFlowFile workFlowFile = new WorkFlowFile();
                    workFlowFile.ShapeType = MasterType.Start;
                    workFlowFile.StepID = stepID;
                    workFlowFile.StepName = "Start";

                    workFlowFile.ShapeNode = shapeNode;

                    return workFlowFile;
                }
            }
            return null;
        }

        public WorkFlowFile GetPreviousRule(string workFlowName, string PreviousID, string currentID)
        {
            XmlDocument xmlDoc = GetWorkFlowXml(workFlowName);
            XmlNodeList connectNodeList = xmlDoc.SelectNodes($"//Connect[@ToSheet='{currentID}']");
            foreach (XmlNode connectNode in connectNodeList)
            {
                string fromSheet = connectNode.Attributes["FromSheet"].Value;
                string toSheet = connectNode.Attributes["ToSheet"].Value;
                string fromCell = connectNode.Attributes["FromCell"].Value;
                if (toSheet == currentID && fromCell == "EndX")
                {
                    XmlNode PreviousNode = xmlDoc.SelectSingleNode($"//Connect[@ToSheet='{PreviousID}' and @FromCell='BeginX' and @FromSheet='{fromSheet}']");
                    if (PreviousNode == null)
                    {
                        continue;
                    }
                    XmlNode lineShape = xmlDoc.SelectSingleNode($"//Shape[@ID='{fromSheet}']");
                    string lineMasterId = lineShape.Attributes["Master"].Value;
                    XmlNode lineMastarNode = xmlDoc.SelectSingleNode($"//Master[@ID='{lineMasterId}']");
                    MasterType lineMasterType = GetMasterType(lineMastarNode.Attributes["BaseID"].Value);
                    WorkFlowFile lineFile = new WorkFlowFile();
                    lineFile.ShapeType = lineMasterType;
                    lineFile.StepID = fromSheet;
                    lineFile.StepName = lineShape.Attributes["Name"].Value;
                    lineFile.ShapeNode = lineShape;
                    return lineFile;
                }
            }
            return null;
        }
        #endregion

        #region Parameters
        public List<ParameterEntity> GetInParametersByDetailID(string detailID)
        {
            List<ParameterEntity> result = new List<ParameterEntity>();
            //Get workflowinfo
            WorkFlowInfo workFlowInfo = _DataManager.GetWorkFlowInfoByDetailID(detailID);
            //Get workflowDetail
            WorkFlowDetail workFlowDetail = _DataManager.GetWorkFlowDetailByID(detailID);
            // Get global parameters
            List<ParameterEntity> globalParameters = _DataManager.GetGlobalParametersByMainID(workFlowInfo.ID);
            // Get WorkFlowFile
            WorkFlowFile workFlowFile = GetWorkFlowFileByStepID(workFlowInfo.WorkFlowName, workFlowDetail.StepID);
            // Get all parameters
            List<ParameterEntity> allParameters = _DataManager.GetAllParameterEntityByMainID(workFlowInfo.ID);
            foreach (Parameter parameter in workFlowFile.Config.InParameters)
            {
                string newValue = parameter.Value;
                if (Regex.IsMatch(parameter.Value, "^\\{.*?\\}$") && parameter.Value.Length > 2)
                {
                    string key = parameter.Value.Substring(1, parameter.Value.Length - 2);
                    ParameterEntity parameterEntity = allParameters.Find(a => a.Name == key);
                    if (parameterEntity != null)
                    {
                        newValue = parameterEntity.Value;
                    }
                }
                ParameterEntity parameterItem = new ParameterEntity();
                parameterItem.Name = parameter.Name;
                parameterItem.Value = newValue;
                parameterItem.DetailID = detailID;
                result.Add(parameterItem);
            }
            // Add globalParameters
            foreach (ParameterEntity parameterEntity in globalParameters)
            {
                if (result.Find(a => a.Name == parameterEntity.Name) == null)
                {
                    result.Add(parameterEntity);
                }
            }
            return result;
        }

        public List<ParameterEntity> GetOutParametersByDetailID(string detailID)
        {
            List<ParameterEntity> result = new List<ParameterEntity>();
            //Get workflowinfo
            WorkFlowInfo workFlowInfo = _DataManager.GetWorkFlowInfoByDetailID(detailID);
            //Get workflowDetail
            WorkFlowDetail workFlowDetail = _DataManager.GetWorkFlowDetailByID(detailID);
            // Get global parameters
            List<ParameterEntity> globalParameters = _DataManager.GetGlobalParametersByMainID(workFlowInfo.ID);
            // Get WorkFlowFile
            WorkFlowFile workFlowFile = GetWorkFlowFileByStepID(workFlowInfo.WorkFlowName, workFlowDetail.StepID);
            // Get all parameters
            List<ParameterEntity> allParameters = _DataManager.GetAllParameterEntityByMainID(workFlowInfo.ID);
            foreach (Parameter parameter in workFlowFile.Config.OutParameters)
            {
                string newValue = parameter.Value;
                if (string.IsNullOrEmpty(newValue))
                {
                    ParameterEntity parameterEntity = allParameters.Find(a => a.Name == parameter.Name);
                    if (parameterEntity != null)
                    {
                        newValue = parameterEntity.Value;
                    }
                }
                else if (Regex.IsMatch(parameter.Value, "^\\{.*?\\}$") && parameter.Value.Length > 2)
                {
                    string key = parameter.Value.Substring(1, parameter.Value.Length - 2);
                    ParameterEntity parameterEntity = allParameters.Find(a => a.Name == key);
                    if (parameterEntity != null)
                    {
                        newValue = parameterEntity.Value;
                    }
                }
                ParameterEntity parameterItem = new ParameterEntity();
                parameterItem.Name = parameter.Name;
                parameterItem.Value = newValue;
                parameterItem.DetailID = detailID;
                result.Add(parameterItem);
            }
            // Add globalParameters
            foreach (ParameterEntity parameterEntity in globalParameters)
            {
                if (result.Find(a => a.Name == parameterEntity.Name) == null)
                {
                    result.Add(parameterEntity);
                }
            }
            return result;
        }
        #endregion
    }
}
